#include "Ray.h"
#include "BoundingBox.h"
#include "Math.h"

Ray::Ray()
{
	mOrigin = glm::vec3(0.0f);
	mDirection = glm::vec3(0.0f);
}

Ray::Ray(const glm::vec3& origin, const glm::vec3& direction)
{
	mOrigin = origin;
	mDirection = direction;
}

bool Ray::Intersects(const BoundingBox& bb, float& t) const
{
	t = 0.0f;
	// Check for ray origin being inside the bb
	if (bb.IsInside(mOrigin))
		return true;

	float dist = Math::M_INFINITY;

	// Check for intersecting in the X-direction
	if (mOrigin.x < bb.Min().x && mDirection.x > 0.0f)
	{
		float x = (bb.Min().x - mOrigin.x) / mDirection.x;
		if (x < dist)
		{
			glm::vec3 point = mOrigin + x * mDirection;
			if (point.y >= bb.Min().y && point.y <= bb.Max().y && point.z >= bb.Min().z && point.z <= bb.Max().z)
				dist = x;
		}
	}
	if (mOrigin.x > bb.Max().x && mDirection.x < 0.0f)
	{
		float x = (bb.Max().x - mOrigin.x) / mDirection.x;
		if (x < dist)
		{
			glm::vec3 point = mOrigin + x * mDirection;
			if (point.y >= bb.Min().y && point.y <= bb.Max().y && point.z >= bb.Min().z && point.z <= bb.Max().z)
				dist = x;
		}
	}
	// Check for intersecting in the Y-direction
	if (mOrigin.y < bb.Min().y && mDirection.y > 0.0f)
	{
		float x = (bb.Min().y - mOrigin.y) / mDirection.y;
		if (x < dist)
		{
			glm::vec3 point = mOrigin + x * mDirection;
			if (point.x >= bb.Min().x && point.x <= bb.Max().x && point.z >= bb.Min().z && point.z <= bb.Max().z)
				dist = x;
		}
	}
	if (mOrigin.y > bb.Max().y && mDirection.y < 0.0f)
	{
		float x = (bb.Max().y - mOrigin.y) / mDirection.y;
		if (x < dist)
		{
			glm::vec3 point = mOrigin + x * mDirection;
			if (point.x >= bb.Min().x && point.x <= bb.Max().x && point.z >= bb.Min().z && point.z <= bb.Max().z)
				dist = x;
		}
	}
	// Check for intersecting in the Z-direction
	if (mOrigin.z < bb.Min().z && mDirection.z > 0.0f)
	{
		float x = (bb.Min().z - mOrigin.z) / mDirection.z;
		if (x < dist)
		{
			glm::vec3 point = mOrigin + x * mDirection;
			if (point.x >= bb.Min().x && point.x <= bb.Max().x && point.y >= bb.Min().y && point.y <= bb.Max().y)
				dist = x;
		}
	}
	if (mOrigin.z > bb.Max().z && mDirection.z < 0.0f)
	{
		float x = (bb.Max().z - mOrigin.z) / mDirection.z;
		if (x < dist)
		{
			glm::vec3 point = mOrigin + x * mDirection;
			if (point.x >= bb.Min().x && point.x <= bb.Max().x && point.y >= bb.Min().y && point.y <= bb.Max().y)
				dist = x;
		}
	}

	t = dist;
	return dist < Math::M_INFINITY;;
}

bool Ray::Intersects(const BoundingBox& bb) const
{
	float distance;
	return Intersects(bb, distance);
}

bool Ray::IntersectsTriangle(const glm::vec3& a, const glm::vec3& b, const glm::vec3& c, float& t) const
{
	const glm::vec3 E1 = b - a;
	const glm::vec3 E2 = c - a;
	const glm::vec3 N = cross(E1, E2);
	const float det = -glm::dot(mDirection, N);
	const float invdet = 1.f / det;
	const glm::vec3 AO = mOrigin - a;
	const glm::vec3 DAO = glm::cross(AO, mDirection);
	const float u = glm::dot(E2, DAO) * invdet;
	const float v = -glm::dot(E1, DAO) * invdet;
	t = glm::dot(AO, N) * invdet;
	return (det >= 1e-6f && t >= 0.0f && u >= 0.0f && v >= 0.0f && (u + v) <= 1.0f);
}